/*

MG	Meridian Gravitas

*/

#include <stdio.h>
#include <ctype.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>
#include "standard.h"
#include "hs.h"
#include "g.h"

#ifdef EGA
#include "egag2.h"
#endif

#ifdef CGA
#include "cgag2.h"
#endif

#include "rawkey.h"
#include "tick.h"
#include "se.h"
#include "mus.h"
#include "sprites.h"
#include "effects.h"
#include "music.h"

/*...vhs\46\h:0:*/
/*...vg\46\h:0:*/
/*...vegag2\46\h:0:*/
/*...vcgag2\46\h:0:*/
/*...vrawkey\46\h:0:*/
/*...vtick\46\h:0:*/
/*...vse\46\h:0:*/
/*...vmus\46\h:0:*/
/*...vsprites\46\h:0:*/
/*...veffects\46\h:0:*/
/*...vmusic\46\h:0:*/

#ifdef EGA
char	progname [] = "egamg";
static char sccs_id [] = "@(#)EGA version of Meridian Gravitas  8/8/94";
#endif
#ifdef CGA
char	progname [] = "cgamg";
static char sccs_id [] = "@(#)CGA version of Meridian Gravitas  8/8/94";
#endif

/*...sfatal:0:*/
static void fatal(const char *fmt, ...)
	{
	va_list	vars;
	char	s [256+1];

	va_start(vars, fmt);
	vsprintf(s, fmt, vars);
	va_end(vars);
	fprintf(stderr, "%s: %s\n", progname, s);
	exit(1);
	}
/*...e*/

#define	HS_FN		"mg.scr"
#define	srandom(seed)	srand(seed)
#define	random()	rand()

/*...sresolution stuff:0:*/
#define	X_MIN	8
#define	X_MID	160
#define	X_MAX	311
#define	X_RES	304
#define	Y_MIN	30
#define	Y_MID	116
#define	Y_MAX	197
#define	Y_RES	168
#define	X_CHAR	40
#define	Y_CHAR	25
/*...e*/
/*...svars:0:*/
static int joystick;

#define	ASCEND	0x0100
#define	DESCEND	0x0200

static int last_move;			/* direction of last ok move         */
static BOOLEAN firing;			/* are we firing a lazer bolt        */
static int l_direction;			/* lazer bolt direction              */
static int lx1, ly1, lz1, lx2, ly2, lz2;/* lazer bolt endpoint coords        */

#define	VIS_PAGE	0
#define	SPR_PAGE	1
#define	ALT_PAGE	2

static int modulo_8;

static int n_setable;			/* how many squares are setable      */
static int n_set;			/* how many have we set so far       */

/*
Bastards. ie: any meanie out to kill you.
*/

#define	BT_BALL		0		/* bastard is a stripey ball         */
#define	BT_STAR		1		/* bastard is a spripey cross        */
#define	BT_TRI		2		/* bastard is a hollow triangle      */
#define	BT_UD		3		/* bastard goes away/towards you     */
#define	BT_LR		4		/* bastard goes left and right       */
#define	BT_MAX		5

static int bastard_sprites [] = { SPR_BALL_0, SPR_STAR_0, SPR_TRI_0, SPR_UD_0, SPR_LR_0 };

#define	B_ALIVE		0x0001		/* bastard is currently alive        */
#define	B_PLOTTED	0x0002		/* was alive last cycle, so erase    */
#define	B_KILLABLE	0x0004		/* can bastard be killed             */
#define	B_DYING		0x0008		/* is bastard exploding              */

typedef struct bastard_struct
	{
	char	bastard_type;		/* holds type such as BT_BALL        */
	char	bastard_state;		/* is it alive, etc.                 */
	char	bastard_mode;		/* what sort of motion is occuring   */
	char	bastard_dir;		/* direction flags                   */
	char	bastard_cnt;		/* bastards internal counter         */
	int	bastard_x;		/* current coordinates               */
	int	bastard_y;
	int	bastard_z;
	int	bastard_old_x;		/* coords of where drawn last time   */
	int	bastard_old_y;
	int	bastard_old_z;
	} BASTARD;

#define	MAX_BASTARDS	5

BASTARD	bastards [MAX_BASTARDS];
static int n_bastards;

/*
The player position in units is given by :-

	(psx * SQR_PER_SCN + pqx) * SIDE + pux
	(psz * SQR_PER_SCN + pqz) * SIDE + puz
*/

static int psx, psz;			/* player screen x and z             */
static int pqx, pqz;			/* player square x and z             */
static int pux, puz;			/* player unit x and z               */
static int py;				/* player y range 20 .. 80           */
static int px,  pz;			/* pq? * SIDE + pu?                  */
static int qx,  qz;			/* ps? * SQR_PER_SCN + pq?           */
static int sx,  sz;			/* ps? * SQR_PER_SCN                 */

static int sx_vis, sz_vis;		/* sx and sz currently in force      */

static int old_psx, old_psz;		/* for change over of screen check   */

static int entry_pqx, entry_pqz;	/* position entered screen at        */

#define	SCORE_SCN	10L		/* score for visiting a new screen   */
#define	SCORE_SET	50L		/* score for setting a square        */
#define	SCORE_KILL	20L		/* score for killing a nasty         */

static long score;			/* your score                        */
static int x_factor;			/* multiplying factor for scores     */
					/* range 100.. 9900                  */

static BOOLEAN dead;			/* are you living                    */
static int lives;			/* how many lives you have left      */

/*
Data that is unique to a screen.
*/

typedef struct scn_struct
	{
	BOOLEAN	scn_visited;
	int	scn_palette;
	int	scn_n_bastards [BT_MAX];
	} SCN;

#define	SCN_ARRAY_X_SZ	12
#define	SCN_ARRAY_Z_SZ	12

static SCN scns [SCN_ARRAY_X_SZ][SCN_ARRAY_Z_SZ];

/*
Data relevant to each  square on a screen.
*/

#define	SIDE		16		/* a SQR is SIDE UNITS wide          */
#define	HALF		8
#define	QTR		4
#define	EIGHTH		2

#define	STEP		2		/* units to move player each time    */
#define	FIDDLE		1

#define	CLOSE		4		/* how close can get to a wall       */
					/* and width of door frame pillars   */
#define	PWID		2

#define	ROOT3_4		7

static int cos_table [] =
	{ HALF, ROOT3_4, QTR,
	  0, -QTR, -ROOT3_4,
	  -HALF, -ROOT3_4, -QTR,
	  0, QTR, ROOT3_4 };

#define	rough_cos(a)	cos_table [(a)%12]
#define	rough_sin(a)	cos_table [((a)-3)%12]

#define	SQR_PER_SCN	5		/* SQRS per SCN (both x & z)         */

#define	T_VISIBLE	0x0001		/* is it visible as a surface        */
#define	T_MINUS		0x0002		/* does it have a - on it            */
#define	T_BAR		0x0004		/* does it have a | on it            */
#define	T_BORDER	0x0008		/* does it have a border             */
#define	T_DIAMOND	0x0010		/* does it have a diamond on it      */
#define	T_X		0x0020		/* has it got an X on it             */
#define	T_POLE		0x0040		/* is it supported by a thin pole    */
#define	T_SOLID		0x0080		/* does it have a drape              */
#define	T_HANGS		0x0100		/* does it hang from above           */
#define	T_SET		0x0200		/* has it been set with a small sqr  */
#define	T_SETABLE	0x0400		/* can it be set                     */
#define	T_USED		0x0800		/* has the X factor been used        */

#define	T_ARROW_LEFT	0x1000
#define	T_ARROW_RIGHT	0x2000
#define	T_ARROW_UP	0x4000
#define	T_ARROW_DOWN	0x8000

#define	TD_BLOWER	(T_MINUS|T_BAR|T_DIAMOND)
#define	TD_CROSS	(T_MINUS|T_BAR)

typedef struct sqr_struct
	{
	int	sqr_type;		/* combination of T_ values above    */
	int	sqr_y;			/* height of square in range 0 - 80  */
	char	sqr_num;		/* number used variously             */
	} SQR;

#define	SQR_ARRAY_X_SZ	(SCN_ARRAY_X_SZ * SQR_PER_SCN)
#define	SQR_ARRAY_Z_SZ	(SCN_ARRAY_Z_SZ * SQR_PER_SCN)

static SQR sqrs [SQR_ARRAY_X_SZ][SQR_ARRAY_Z_SZ];
/*...e*/
/*...spalette stuff:0:*/
static int palette;

static int palettes [][8] =
	{
		{ G_4,  G_5,  G_7, G_15, G_0,  G_2, G_5, G_3 },
		{ G_1, G_2,  G_7, G_15, G_0,  G_6, G_3, G_5 },
		{ G_4, G_7, G_7, G_14, G_3, G_2, G_5, G_11 },
		{ G_6, G_4, G_15, G_14, G_7, G_8, G_3, G_13 },
		{ G_1, G_9, G_5, G_0, G_6, G_5, G_11, G_9 },
		{ G_2, G_10, G_15, G_1, G_8, G_9, G_13, G_13 }
	};

#define	N_PALETTE	(sizeof(palettes)/(8 * sizeof(int)))

#define	C_FRONT		0
#define	C_SIDE		1
#define	C_WIRE		2
#define	C_MARK		3
#define	C_SET		4
#define	C_TOP		5
#define	C_GRID		6
#define	C_DOOR		7

#define	COLOUR(c)	(palettes [palette][c])
/*...e*/
/*...sx_pos and y_pos:0:*/
/*
Use equations :-

	q = SQR_PER_SCN * SIDE

	sc_x = { (q * x) / (z + q) } * ( X_RES / (SQR_PER_SCN * SIDE) ) + X_MID
	sc_y = { (q * y) / (z + q) } * ( Y_RES / (SQR_PER_SCN * SIDE) ) + Y_MIN

Note: z in range 0 .. 80, x in range -40 .. +40, y in range 0 .. 80
*/


#define	x_pos(x, z)	(((x) * X_RES) / ((z) + SQR_PER_SCN * SIDE)) + X_MID
#define	y_pos(y, z)	(((y) * Y_RES) / ((z) + SQR_PER_SCN * SIDE)) + Y_MIN
/*...e*/
/*...sdistance calculations:0:*/
static int d_cvt [SQR_PER_SCN * SIDE + 1];

static
void	initialise_dist_table()
	{
	int	i;

	for ( i = 0; i <= SQR_PER_SCN * SIDE; i++ )
		d_cvt [i] = 2 * (i / SIDE) + (i % SIDE != 0);
	}

static
int	dist(x, y, z)
int	x, y, z;
	{
	if ( x < 2 * SIDE + HALF )
		x = SQR_PER_SCN * SIDE - x;
	return ( (y << 8) + (d_cvt [z] << 4) + d_cvt [x] );
	}
/*...e*/
/*...sdisplay_sqr:0:*/
/*
Nomenclature for places on the square :-

           TL    +     TM    +     TR	<- y0


          +     QTL   +    QTR   +	<- y1


         LM    +     +     +    RM	<- y2


       +     QBL    +     QBR   +	<- y3


     BL    +     BM    +     BR		<- y4


*/

static
void	display_sqr(sqr, x, z)			/* display to screen         */
SQR	*sqr;					/* square to display         */
int	x;					/* range -40 .. 40           */
int	z;					/* range 0 .. 80             */
	{
	int	y, type;
	int	y0, y1, y2, y3, y4;
	int	y0g, y0c,y4g, y4c, y1g, y3g, y0d, y4d;
	int	tlx, tmx, trx;
	int	qtlx, qtrx;
	int	lmx, mx, rmx;
	int	qblx, qbrx;
	int	blx, bmx, brx;
	int	mid;
	int	dt, dl, dm, dr, db;

	if ( ((type = sqr -> sqr_type) & T_VISIBLE) )
		{
		y = sqr -> sqr_y;

/*...scalc y0\44\ y4:16:*/
y0 = y_pos(y, z + SIDE);
y4 = y_pos(y, z);
/*...e*/
/*...scalc tlx\44\ trx\44\ blx\44\ brx:16:*/
tlx = x_pos(x,        z + SIDE);
trx = x_pos(x + SIDE, z + SIDE);
blx = x_pos(x,        z);
brx = x_pos(x + SIDE, z);
/*...e*/
/*...sif reqd\46\ calc y2\44\ lmx\44\ rmx:16:*/
if ( type & (T_MINUS|T_DIAMOND|T_ARROW_LEFT|T_ARROW_RIGHT) )
	/* need mid points of left and right sides */
	{
	y2  = y_pos(y,        z + HALF);
	lmx = x_pos(x,        z + HALF);
	rmx = x_pos(x + SIDE, z + HALF);
	}
/*...e*/
/*...sif reqd\46\ calc tmx\44\ bmx:16:*/
if ( type & (T_BAR|T_DIAMOND|T_ARROW_UP|T_ARROW_DOWN) )
	/* need mid points of top and bottom sides */
	{
	tmx = x_pos(x + HALF, z + SIDE);
	bmx = x_pos(x + HALF, z);
	}
/*...e*/
/*...sif reqd\46\ calc y1\44\ y3\44\ qtlx\44\ qtrx\44\ qblx\44\ qbry:16:*/
if ( type & (T_SET|T_SETABLE|T_POLE|T_ARROW_LEFT|T_ARROW_RIGHT|T_ARROW_UP|T_ARROW_DOWN|T_USED) )
	/* need quarter points */
	{
	y1   = y_pos(y, z + HALF + QTR);
	y3   = y_pos(y, z + QTR);
	qtlx = x_pos(x + QTR,        z + HALF + QTR);
	qtrx = x_pos(x + HALF + QTR, z + HALF + QTR);
	qblx = x_pos(x + QTR,        z + QTR);
	qbrx = x_pos(x + HALF + QTR, z + QTR);
	}
/*...e*/
/*...sif reqd\46\ calc y0g\44\ y4g:16:*/
if ( type & T_SOLID )
	/* need coords of floor */
	{
	y0g = y_pos(80, z + SIDE);
	y4g = y_pos(80, z);
	}
/*...e*/
/*...sif reqd\46\ calc y1g\44\ y3g:16:*/
if ( type & T_POLE )
	/* need coords of floor */
	{
	y1g = y_pos(80, z + HALF + QTR);
	y3g = y_pos(80, z + QTR);
	}
/*...e*/
/*...sif reqd\46\ calc y0c\44\ y4c:16:*/
if ( type & T_HANGS )
	/* need coords of ceiling */
	{
	y0c = y_pos(5, z + SIDE);
	y4c = y_pos(5, z);
	}
/*...e*/
/*...sif reqd\46\ calc y0d\44\ y4d:16:*/
if ( type & (T_HANGS|T_ARROW_UP|T_ARROW_DOWN|T_ARROW_RIGHT|T_ARROW_LEFT) )
	/* calculate door tops */
	{
	y0d = y_pos(y - 15, z + SIDE);
	y4d = y_pos(y - 15, z);
	}
/*...e*/

		dt = dist(0, y, z + SIDE);
		dl = dist(x + 2 * SIDE + HALF,        y, z + HALF);
		dm = dist(x + 2 * SIDE + HALF + HALF, y, z + HALF);
		dr = dist(x + 2 * SIDE + HALF + SIDE, y, z + HALF);
		db = dist(0, y, z);

		if ( type & T_HANGS )
			/* draw lines to the ceiling at back */
			{
			g_l_line(dt, tlx, y0, tlx, y0c, COLOUR(C_WIRE));
			g_l_line(dt, trx, y0, trx, y0c, COLOUR(C_WIRE));
			}

		if ( type & T_POLE )
			/* display pole */
			{
			g_l_rect(dm, qtlx, y1, qtrx, y3g, COLOUR(C_SIDE));
			g_l_rect(dm, qblx, y3, qbrx, y3g, COLOUR(C_FRONT));
			/* now remove a small triangular part */
			if ( qtlx < qblx )
				g_l_vert_diag(dm, qtlx, qtlx, y1g, qblx, y3g, G_0);
			else if ( qbrx < qtrx )
				g_l_diag_vert(dm, qtrx, qtrx, y1g, qbrx, y3g, G_0);
			}

		mid = ( (tlx + trx + blx + brx) >> 2 );
		g_l_diag_vert(dm, mid, tlx, y0, blx, y4, COLOUR(C_TOP));
		g_l_vert_diag(dm, mid, trx, y0, brx, y4, COLOUR(C_TOP));

		if ( type & T_SOLID )
			/* draw a drape over the front and sides */
			{
			g_l_rect(db, blx, y4, brx, y4g, COLOUR(C_FRONT));
			if ( tlx < blx )
				{
				g_l_vert_diag(dt, tlx, blx, y4, tlx, y0, COLOUR(C_SIDE));
				g_l_rect(dl, tlx, y4, blx, y4g, COLOUR(C_SIDE));
				g_s_vert_diag(tlx, tlx, y0g, blx, y4g, G_0);
				g_s_line(tlx, y4g, blx, y4g, COLOUR(C_GRID));
				}
			else if ( brx < trx )
				{
				g_l_diag_vert(dt, trx, brx, y4, trx, y0, COLOUR(C_SIDE));
				g_l_rect(dr, trx, y4, brx, y4g, COLOUR(C_SIDE));
				g_s_diag_vert(trx, trx, y0g, brx, y4g, G_0);
				g_s_line(trx, y4g, brx, y4g, COLOUR(C_GRID));
				}
			}

		if ( type & T_USED )
			/* do a little X on the surface */
			{
			g_s_line(qtlx, y1, qbrx, y3, COLOUR(C_MARK));
			g_s_line(qtrx, y1, qblx, y3, COLOUR(C_MARK));
			}
		else if ( type & T_X )
			/* do an X on the surface */
			{
			g_s_line(tlx, y0, brx, y4, COLOUR(C_MARK));
			g_s_line(trx, y0, blx, y4, COLOUR(C_MARK));
			}
		if ( type & T_MINUS )
			/* do an - on the surface */
			g_s_line(lmx, y2, rmx, y2, COLOUR(C_MARK));
		if ( type & T_BAR )
			/* do an | on the surface */
			g_s_line(tmx, y0, bmx, y4, COLOUR(C_MARK));
		if ( type & T_DIAMOND )
			/* do a diamond on the surface */
			{
			g_s_line(tmx, y0, rmx, y2, COLOUR(C_MARK));
			g_s_line(rmx, y2, bmx, y4, COLOUR(C_MARK));
			g_s_line(bmx, y4, lmx, y2, COLOUR(C_MARK));
			g_s_line(lmx, y2, tmx, y0, COLOUR(C_MARK));
			}
		if ( type & T_SET )
			/* draw a square on the surface */
			{
			mid = ( (qtlx + qtrx + qblx + qbrx) >> 2 );
			g_s_diag_vert(mid, qtlx, y1, qblx, y3, COLOUR(C_SET));
			g_s_vert_diag(mid, qtrx, y1, qbrx, y3, COLOUR(C_SET));
			}
		else if ( type & T_SETABLE )
			{
			g_s_line(qtlx, y1, qtrx, y1, COLOUR(C_MARK));
			g_s_line(qtrx, y1, qbrx, y3, COLOUR(C_MARK));
			g_s_line(qbrx, y3, qblx, y3, COLOUR(C_MARK));
			g_s_line(qblx, y3, qtlx, y1, COLOUR(C_MARK));
			}
		if ( type & T_BORDER )
			/* draw a ring around the edge */
			{
			g_s_line(tlx, y0, trx, y0, COLOUR(C_MARK));
			g_s_line(blx, y4, brx, y4, COLOUR(C_MARK));
			g_s_line(tlx, y0, blx, y4, COLOUR(C_MARK));
			g_s_line(trx, y0, brx, y4, COLOUR(C_MARK));
			}
		if ( type & T_ARROW_LEFT )
			/* draw an arrow going left */
			{
			g_s_line(qtlx, y1, lmx, y2, COLOUR(C_MARK));
			g_s_line(lmx, y2, qblx, y3, COLOUR(C_MARK));
			g_s_line(qblx, y3, qtlx, y1, COLOUR(C_MARK));
			g_s_diag_vert(tlx, tlx, y0d, blx, y4d, COLOUR(C_DOOR));
			g_s_rect(blx, y4d, tlx, y0, COLOUR(C_DOOR));
			g_s_vert_diag(blx, tlx, y0, blx, y4, COLOUR(C_TOP));
			}
		if ( type & T_ARROW_RIGHT )
			/* draw an arrow going right */
			{
			g_s_line(qtrx, y1, rmx, y2, COLOUR(C_MARK));
			g_s_line(rmx, y2, qbrx, y3, COLOUR(C_MARK));
			g_s_line(qbrx, y3, qtrx, y1, COLOUR(C_MARK));
			g_s_vert_diag(trx, trx, y0d, brx, y4d, COLOUR(C_DOOR));
			g_s_rect(brx, y4d, trx, y0, COLOUR(C_DOOR));
			g_s_diag_vert(brx, trx, y0, brx, y4, COLOUR(C_TOP));
			}
		if ( type & T_ARROW_UP )
			/* draw an arrow going up */
			{
			g_s_line(qtlx, y1, tmx, y0, COLOUR(C_MARK));
			g_s_line(tmx, y0, qtrx, y1, COLOUR(C_MARK));
			g_s_line(qtrx, y1, qtlx, y1, COLOUR(C_MARK));
			g_s_rect(tlx + 2, y0d + 2, trx - 2, y0, G_0);
			g_s_rect(tlx, y0d, trx, y0d + 2, COLOUR(C_DOOR));
			g_s_rect(tlx, y0d + 2, tlx + 2, y0, COLOUR(C_DOOR));
			g_s_rect(trx - 2, y0d + 2, trx, y0, COLOUR(C_DOOR));
			}
		if ( type & T_ARROW_DOWN )
			/* draw an arrow going down */
			{
			g_s_line(qblx, y3, bmx, y4, COLOUR(C_MARK));
			g_s_line(bmx, y4, qbrx, y3, COLOUR(C_MARK));
			g_s_line(qbrx, y3, qblx, y3, COLOUR(C_MARK));
			g_l_rect(0, blx, y4d, brx, y4d + 4, COLOUR(C_DOOR));
			g_l_rect(0, blx, y4d + 4, blx + 4, y4, COLOUR(C_DOOR));
			g_l_rect(0, brx - 4, y4d + 4, brx, y4, COLOUR(C_DOOR));
			}
		if ( type & T_HANGS )
			/* draw lines to the ceiling at front */
			{
			g_l_line(db, blx, y4, blx, y4c, COLOUR(C_WIRE));
			g_l_line(db, brx, y4, brx, y4c, COLOUR(C_WIRE));
			}
		}
	else
		/* not visible, so display a spike */
		{
/*...scalc y1\44\ y2\44\ y3\44\ qtlx\44\ qtrx\44\ qblx\44\ qbry\44\ mx:16:*/
y1   = y_pos(80, z + HALF + QTR);
y2   = y_pos(75, z + HALF);
y3   = y_pos(80, z + QTR);
qtlx = x_pos(x + QTR,        z + HALF + QTR);
qtrx = x_pos(x + HALF + QTR, z + HALF + QTR);
qblx = x_pos(x + QTR,        z + QTR);
qbrx = x_pos(x + HALF + QTR, z + QTR);
mx   = x_pos(x + HALF,       z + HALF);
/*...e*/
		g_l_line(dm, qtlx, y1, mx, y2, COLOUR(C_MARK));
		g_l_line(dm, qtrx, y1, mx, y2, COLOUR(C_MARK));
		g_l_line(dm, qblx, y3, mx, y2, COLOUR(C_MARK));
		g_l_line(dm, qbrx, y3, mx, y2, COLOUR(C_MARK));
		}
	}
/*...e*/
/*...sdisplay_stats:0:*/
static
void	display_stats()
	{
	int	i;
	char	s [6];

	g_s_rect(8, 8, 7 * 8 - 1, 15, G_0);
	for ( i = 0; i < lives; i++ )
		{
		g_s_print("j", i + 1, 1, G_3);
		g_s_print("k", i + 1, 1, G_15);
		}
	sprintf(s, "l%02d", x_factor/100);
	g_s_print(s, 4, 1, G_5);
	g_s_print("m", 4, 1, G_15);
	}
/*...e*/
/*...sdisplay_score:0:*/
static
void	display_score()
	{
	char	s [6+1];

	sprintf(s, "%06ld", score);
	g_s_rect((X_CHAR - 7) * 8, 8, (X_CHAR - 1) * 8 - 1, 15, G_0);
	g_s_print(s, (X_CHAR - 7), 1, G_15);
	}
/*...e*/
/*...sdisplay_background:0:*/
static
void	display_background()
	{
	int	x, y, z, x1, y1, x2, y2, x3, y3, x4, y4;

	y1 = y_pos(80, 0);
	y2 = y_pos(80, 80);
	y3 = y_pos(5,  80);
 	for ( x = -40; x <= 40; x += SIDE )
		{
		x1 = x_pos(x, 0);
		x2 = x_pos(x, 80);
		g_s_line(x1, y1, x2, y2, COLOUR(C_GRID));
		g_s_line(x2, y2, x2, y3, COLOUR(C_GRID));
		}
	for ( z = 0; z <= 80; z += SIDE )
		{
		x1 = x_pos(-40, z);
		x2 = x_pos(40,  z);
		y1 = y_pos(5,   z);
		y2 = y_pos(80,  z);
		g_s_line(x1, y1, x1, y2, COLOUR(C_GRID));
		g_s_line(x1, y2, x2, y2, COLOUR(C_GRID));
		g_s_line(x2, y2, x2, y1, COLOUR(C_GRID));
		}
	x1 = x_pos(-40, 0);
	x2 = x_pos(-40, 80);
	x3 = x_pos(40,  80);
	x4 = x_pos(40,  0);
	for ( y = 10; y <= 80; y += 10 )
		{
		y1 = y_pos(y, 0);
		y2 = y_pos(y, 80);
		g_s_line(x1, y1, x2, y2, COLOUR(C_GRID));
		g_s_line(x2, y2, x3, y2, COLOUR(C_GRID));
		g_s_line(x3, y2, x4, y1, COLOUR(C_GRID));
		}
	}
/*...e*/
/*...sdisplay_scn:0:*/
static int order [] = { 0, 1, 4, 3, 2 };

static
void	display_scn(scn_x, scn_z)	/* draws it in the alternate page */
int	scn_x, scn_z;
	{
	int	x, z, i, j;
	char	s [2];

	palette = scns [scn_x][scn_z].scn_palette;

	g_clear_log();

	g_s_update(ALT_PAGE);

	g_s_clear(G_0);

	g_s_print("deeeeeef", 0, 0, G_3);
	g_s_print("b      c", 0, 1, G_3);
	g_s_print("ghhhhhhi", 0, 2, G_3);

	g_s_print("deeeeeef", X_CHAR - 8, 0, G_3);
	g_s_print("b      c", X_CHAR - 8, 1, G_3);
	g_s_print("ghhhhhhi", X_CHAR - 8, 2, G_3);

	s [1] = '\0';
	for ( i = 0; i < 3; i++ )
		for ( j = 0; j < 16; j++ )
			{
			s [0] = 128 + i * 16 + j;
			g_s_print(s, (X_CHAR/2) - 8 + j, i, G_15);
			}

	display_stats();
	display_score();
	display_background();

	for ( z = 4; z >= 0; z-- )
		for ( i = 0; i <= 4; i++ )
			{
			x = order [i];
			display_sqr(&(sqrs [scn_x * SQR_PER_SCN + x][scn_z * SQR_PER_SCN + z]),
				(x - 2) * SIDE - HALF, z * SIDE);
			}

	sx_vis = sx; sz_vis = sz;

	g_s_update(VIS_PAGE);
	}
/*...e*/
/*...sprecalc_object\44\   undisplay_object:0:*/
static
void	precalc_object(px, py, pz, shadow, sprite)
int	px, py, pz;
BOOLEAN	shadow;
int	sprite;
	{
	int	y, xp;

	xp = x_pos(px - 2 * SIDE - HALF, pz) - 8;
	if ( shadow )
		{
		y = sqrs [sx_vis + px/SIDE][sz_vis + pz/SIDE].sqr_y;
		g_precalc(dist(px + FIDDLE, y, pz), pz , SPR_SHADOW_0 + pz/SIDE, xp, y_pos(y, pz) - 16);
		}
	g_precalc(dist(px + FIDDLE, py, pz), pz, sprite, xp, y_pos(py, pz) - 16 - 3);
	}

static
void	undisplay_object(px, py, pz, shadow)
int	px, py, pz;
BOOLEAN	shadow;
	{
	int	y, xp;

	xp = x_pos(px - 2 * SIDE - HALF, pz) - 8;
	if ( shadow )
		{
		y = sqrs [sx_vis + px/SIDE][sz_vis + pz/SIDE].sqr_y;
		g_unsprite(ALT_PAGE, SPR_PAGE, xp, y_pos(y, pz) - 16);
		}
	g_unsprite(ALT_PAGE, SPR_PAGE, xp, y_pos(py, pz) - 16 - 3);
	}
/*...e*/
/*...sprecalc_player\44\   undisplay_player:0:*/
static
void	precalc_player(px, py, pz, base)
int	px, py, pz, base;
	{
	precalc_object(px, py, pz, TRUE, base + pz / SIDE);
	}

static
void	undisplay_player(px, py, pz)
int	px, py, pz;
	{
	undisplay_object(px, py, pz, TRUE);
	}
/*...e*/
/*...sprecalc_bastards\44\ undisplay_bastards:0:*/
static
void	precalc_bastards()
	{
	int	i, state, x, y, z, cnt, spr;
	BASTARD	*b;

	b = bastards;
	for ( i = 0; i < n_bastards; i++ )
		{
		if ( (state = b -> bastard_state) & (B_ALIVE|B_DYING) )
			{
			x   = b -> bastard_x;
			y   = b -> bastard_y;
			z   = b -> bastard_z;
			cnt = b -> bastard_cnt;
			spr = ( state & B_ALIVE ) ?
				bastard_sprites [b -> bastard_type] + z / SIDE :
				SPR_EXP_A_0 + 5 * (cnt&1) + z / SIDE;
			precalc_object(x, y, z, TRUE, spr);
			b -> bastard_old_x = x;
			b -> bastard_old_y = y;
			b -> bastard_old_z = z;
			b -> bastard_state |= B_PLOTTED;
			}
		else
			b -> bastard_state &= ~B_PLOTTED;
		b++;
		}
	}

static
void	undisplay_bastards()
	{
	int	i;
	BASTARD	*b;

	b = bastards;
	for ( i = 0; i < n_bastards; i++ )
		{
		if ( b -> bastard_state & B_PLOTTED )
			undisplay_object(b -> bastard_old_x,
					 b -> bastard_old_y,
					 b -> bastard_old_z,
					 TRUE);
		b++;
		}
	}
/*...e*/
/*...scalc_p_q_x\44\ calc_p_q_z:0:*/
static
void	calc_p_q_x()
	{
	px = pqx * SIDE + pux;
	sx = psx * SQR_PER_SCN;
	qx = sx + pqx;
	}

static
void	calc_p_q_z()
	{
	pz = pqz * SIDE + puz;
	sz = psz * SQR_PER_SCN;
	qz = sz + pqz;
	}
/*...e*/
/*...sframe_delay:0:*/
static
void	frame_delay(n)			/* wait n frames, ie: n/50ths sec    */
int	n;
	{
	while ( n-- )
		g_sync();
	}
/*...e*/
/*...scollision stuff:0:*/
/*
3 Collision detect routines to aid detection of impact/alignment of
you and a particular coordinate, to a given degree of accuracy.
*/

static
BOOLEAN	x_in_range_of_player(x, range)
int	x, range;
	{
	return ( x <= px + range && x >= px - range );
	}


static
BOOLEAN	y_in_range_of_player(y, range)
int	y, range;
	{
	return ( y <= py + range && y >= py - range );
	}

static
BOOLEAN	z_in_range_of_player(z, range)
int	z, range;
	{
	return ( z <= pz + range && z >= pz - range );
	}
/*...e*/
/*...sinitialisation stuff:0:*/
void	initialise_zap();	/* forward references */
void	add_score();

static
void	initialise_map()
	{
	int	x, z;

	for ( z = 0; z < SCN_ARRAY_Z_SZ; z++ )
		for ( x = 0; x < SCN_ARRAY_X_SZ; x++ )
			scns [x][z].scn_visited = FALSE;

	n_setable = 0;
	for ( z = 0; z < SQR_ARRAY_Z_SZ; z++ )
		for ( x = 0; x < SQR_ARRAY_X_SZ; x++ )
			{
			sqrs [x][z].sqr_type &= ~(T_SET|T_USED);
			if ( sqrs [x][z].sqr_type & T_SETABLE )
				n_setable++;
			}
	}

static
void	initialise_game()
	{
	n_set     = 0;
	psx       = 0;
	psz       = 0;
	entry_pqx = 2;
	entry_pqz = 0;
	lives     = 3;
	score     = 0L;
	x_factor  = 1000;
	}

static
void	initialise_life()
	{
	lives--;
	dead = FALSE;

	pqx = entry_pqx; pux = HALF;
	pqz = entry_pqz; puz = HALF;
	py = 80;
	calc_p_q_x();
	calc_p_q_z();
	if ( x_factor < 3000 )
		x_factor = 3000;
	last_move = 0;			/* this player not yet moved */
	}

static
int	init_b(inx, n, type)
int	inx, n, type;
	{
	int	i, x, y, z;
	BASTARD	*b;

	b = &bastards [inx];
	for ( i = 0; i < n; i++ )
		{
		b -> bastard_type   = type;
		b -> bastard_state  = B_ALIVE | B_KILLABLE;
		b -> bastard_mode   =
		b -> bastard_cnt    = 0;

		/* note: do not want bastard to appear above player */

		do
			{
			b -> bastard_x = x = (random() % (SQR_PER_SCN - 2) + 1) * SIDE + HALF;
			b -> bastard_z = z = (random() % (SQR_PER_SCN - 2) + 1) * SIDE + HALF;
			}
		while ( x_in_range_of_player(x, HALF) &&
			z_in_range_of_player(x, HALF) );
	
		y = sqrs [sx + x/SIDE][sz + z/SIDE].sqr_y;
	
		b -> bastard_y      = (random() % (y - 15) + 10);
		b -> bastard_old_x  =
		b -> bastard_old_z  =
		b -> bastard_old_y  = -1;
		b++;
		}
	return ( inx + n );
	}

static
void	initialise_screen()
	{
	int	i;

	py = sqrs [qx][qz].sqr_y;

	entry_pqx = pqx;
	entry_pqz = pqz;		/* remember where player came in */

	firing = FALSE;

	if ( !scns [psx][psz].scn_visited )
		{
		scns [psx][psz].scn_visited = TRUE;
		add_score(SCORE_SCN);
		}

	initialise_zap();

	n_bastards = 0;
	for ( i = 0; i < BT_MAX; i++ )
		n_bastards = init_b(n_bastards,
				    scns [psx][psz].scn_n_bastards [i],
				    i);
	}
/*...e*/
/*...smenus stuff:0:*/
static char gack [] = "_";

static
void	print_char(c, x, y, col)
int	c, x, y, col;
	{
	*gack = c;
	g_s_print(gack, x, y, col);
	}

static
void	blank_char(x, y, col)
int	x, y, col;
	{
	g_s_rect(x * 8, y * 8, x * 8 + 7, y * 8 + 7, col);
	}

static
void	get_string(s, len, x, y, f_col, b_col)
char	*s;
int	len;
int	x, y;
int	f_col, b_col;
	{
	int	l = 0, c;

	print_char('a', x, y, G_15);
	while ( (c = rk_wait_keyp()) != '\r' )
		/* deal with the key */
		if ( c == '\b' && l > 0 )
			{
			blank_char(x + l, y, b_col);
			l--;
			blank_char(x + l, y, b_col);
			print_char('a', x + l, y, f_col);
			}
		else if ( (c == ' ' || isalnum(c)) && l < len - 1 )
			{
			blank_char(x + l, y, b_col);
			print_char(c, x + l, y, f_col);
			s [l++] = c;
			print_char('a', x + l, y, f_col);
			}
	blank_char(x + l, y, b_col);
	s [l] = '\0';	/* terminate the string */
	}

static
void	rectangle_around(x1, y1, x2, y2, col)
int	x1, y1, x2, y2, col;
	{
	g_s_rect(x1 * 8 - 4, y1 * 8 - 4, x2 * 8 + 3, y2 * 8 + 3, col);
	}

static
int	title_page()				/* return users choice */
	{
	int	c;

	initialise_map();
	psx = psz = 0;
	calc_p_q_x();
	calc_p_q_z();
	display_scn(0, 0);
	g_s_copy(ALT_PAGE, SPR_PAGE);
	g_s_copy(ALT_PAGE, VIS_PAGE);
	g_s_update(SPR_PAGE);
	g_clear_spr();
	precalc_player(2 * SIDE + HALF, 80, HALF, SPR_PLAYER_0);

	g_sprites();
	g_refresh(SPR_PAGE);
	g_s_update(VIS_PAGE);

	rectangle_around(X_CHAR - 16, Y_CHAR - 4, X_CHAR - 2, Y_CHAR - 1, G_3);
	g_s_print("SPACE TO PLAY",  (X_CHAR-16), (Y_CHAR-4), G_15);
	g_s_print("ENTER HISCORES", (X_CHAR-16), (Y_CHAR-3), G_15);
	g_s_print("ESCAPE QUITS",   (X_CHAR-16), (Y_CHAR-2), G_15);
	do
		{
		rk_wait_release();
		mus_play(MUSIC_TITLE_1, MUSIC_TITLE_2, MUSIC_TITLE_3);
		c = rk_get_joystick();
		}
	while ( c != RK_SPACE && c != RK_ESC && c != RK_RET );

	return ( c );
	}

static
void	reward()
	{
	int	c;

	initialise_map();
	psx = 8; psz = 9;
	calc_p_q_x();
	calc_p_q_z();
	display_scn(8, 9);
	g_s_copy(ALT_PAGE, SPR_PAGE);
	g_s_copy(ALT_PAGE, VIS_PAGE);
	g_s_update(SPR_PAGE);
	g_clear_spr();
	precalc_player(2 * SIDE + HALF, 80, 2 * SIDE + HALF, SPR_PLAYER_0);

	g_sprites();
	g_refresh(SPR_PAGE);
	g_s_update(VIS_PAGE);

	rectangle_around(X_CHAR - 16, Y_CHAR - 4, X_CHAR - 2, Y_CHAR - 1, G_3);
	g_s_print("WELL DONE, YOU", (X_CHAR-16), (Y_CHAR-4), G_15);
	g_s_print("HAVE DONE IT",   (X_CHAR-16), (Y_CHAR-3), G_15);
	g_s_print("ESCAPE QUITS",   (X_CHAR-16), (Y_CHAR-2), G_15);
	do
		{
		rk_wait_release();
		mus_play(MUSIC_REWARD_1, MUSIC_REWARD_2, MUSIC_REWARD_3);
		}
	while ( rk_get_joystick() != RK_ESC );
	}

static
void	display_hiscores()
	{
	char	s [40];
	int	i;

	g_s_copy(SPR_PAGE, VIS_PAGE);
	rectangle_around(5, Y_CHAR - N_HS - 5, X_CHAR - 5, Y_CHAR - 1, G_3);
	g_s_print("HISCORES", 5, Y_CHAR - N_HS - 5, G_5);
	for ( i = 0; i < N_HS; i++ )
		{
		sprintf(s, "%-20s    %06ld", hs_get_name(i), hs_get_score(i));
		g_s_print(s, 5, Y_CHAR - N_HS - 3 + i, G_15);
		}
	g_s_print("PRESS A KEY", X_CHAR - 16, Y_CHAR - 2, G_5);
	rk_wait_release();
	while ( !rk_get_joystick() )
		;
	rk_wait_release();
	}

static
void	update_hiscores()
	{
	int	posn;
	char	name [L_NAME+1];

	g_s_update(VIS_PAGE);
	if ( (posn = hs_where_to_insert(score)) != -1 )
		/* good enough for hiscore table */
		{
		rectangle_around(X_CHAR/2 - 10, Y_CHAR/2 - 1, X_CHAR/2 + 10, Y_CHAR/2 + 2, G_3);
		g_s_print("WELL DONE, PLEASE", X_CHAR/2 - 10, Y_CHAR/2 - 1, G_5);
		g_s_print("ENTER YOUR NAME :-", X_CHAR/2 - 10, Y_CHAR/2, G_5);
		get_string(name, L_NAME, X_CHAR/2 - 10, Y_CHAR/2 + 1, G_15, G_3);
		hs_insert(score, name, posn);
		}
	else
		{
		rectangle_around(X_CHAR/2 - 10, Y_CHAR/2 - 1, X_CHAR/2 + 10, Y_CHAR/2 + 1, G_3);
		g_s_print("NOT GOOD ENOUGH FOR", X_CHAR/2 - 10, Y_CHAR/2 - 1, G_5);
		g_s_print("THE HI-SCORE TABLE", X_CHAR/2 - 10, Y_CHAR/2, G_5);
		frame_delay(50);
		}
	}

static
void	aborted_message()
	{
	g_s_update(VIS_PAGE);
	g_s_rect((X_CHAR/2 - 4) * 8 - 4, (Y_CHAR/2    ) * 8 - 4,
		 (X_CHAR/2 + 3) * 8 + 4, (Y_CHAR/2 + 1) * 8 + 4, G_3);
	g_s_print("ABORTED", (X_CHAR/2-4), (Y_CHAR/2), G_15);
	frame_delay(20);
	}
/*...e*/
/*...sadd_score:0:*/
static
void	add_score(amount)
long	amount;
	{
	long	old_score;

	old_score = score;
	score += amount * (x_factor/2000);
	display_score();
	if ( score/1000 != old_score/1000 && lives < 3 )
		{
		lives++;
		display_stats();
		}
	}
/*...e*/
/*...szapping stuff:0:*/
typedef struct ray_struct
	{
	int	ray_x1, ray_y1;
	int	ray_x2, ray_y2;
	} RAY;

#define	MAX_RAY	30

static RAY ray_table_1 [MAX_RAY];
static int n_ray_1;

static RAY ray_table_2 [MAX_RAY];
static int n_ray_2;

BOOLEAN	first_set;			/* set to update                     */
					/* other set will be removed before  */
					/* first set is written to screen    */

static
void	initialise_zap()
	{
	n_ray_1 = n_ray_2 = 0;
	first_set = TRUE;
	}

static
void	zap(x1, y1, z1, x2, y2, z2)
int	x1, y1, z1, x2, y2, z2;
	{
	int	n_ray;
	RAY	*ray;

	if ( (n_ray = ( first_set ) ? n_ray_1 : n_ray_2) == MAX_RAY )
		return;

	ray = ( first_set ) ? &ray_table_1 [n_ray] : &ray_table_2 [n_ray];

	ray -> ray_x1 = x_pos(x1 - 2 * SIDE - HALF, z1);
	ray -> ray_y1 = y_pos(y1, z1);
	ray -> ray_x2 = x_pos(x2 - 2 * SIDE - HALF, z2);
	ray -> ray_y2 = y_pos(y2, z2);

	if ( first_set )
		n_ray_1++;
	else
		n_ray_2++;
	}

static
void	remove_old_rays()
	{
	int	i;
	RAY	*ray;

	if ( first_set )
		{
		i = n_ray_2; ray = ray_table_2;
		}
	else
		{
		i = n_ray_1; ray = ray_table_1;
		}
	while ( i-- )
		{
		g_s_unline(SPR_PAGE, ray -> ray_x1, ray -> ray_y1,
				     ray -> ray_x2, ray -> ray_y2, G_0);
		ray++;
		}
	}

static
void	display_new_rays()
	{
	int	i;
	RAY	*ray;

	if ( first_set )
		{
		i = n_ray_1; ray = ray_table_1;
		}
	else
		{
		i = n_ray_2; ray = ray_table_2;
		}
	while ( i-- )
		{
		g_s_line(ray -> ray_x1, ray -> ray_y1,
			 ray -> ray_x2, ray -> ray_y2, G_15);
		ray++;
		}

	/* now toggle tables */

	first_set = !first_set;

	if ( first_set )
		n_ray_1 = 0;
	else
		n_ray_2 = 0;
	}
/*...e*/
/*...smove_player:0:*/
#define	fit_through_door(i) ((i) >= PWID && (i) <= SIDE - PWID)

/*
Consider what is below the coordinate feet and return
the maximum height (lowest y) of any squares he/she is on.

Player occupies area of (x +/- PWID, y +/- PWID)
*/

#define	hite(x, z)	(sqrs [(x) / SIDE][(z) / SIDE].sqr_y)

static
int	height(x, z)
int	x, z;
	{
	int	h, max_h;		/* decreases as gets higher */

	max_h = hite(x - PWID, z - PWID);
	if ( (h = hite(x - PWID, z + PWID)) < max_h ) max_h = h;
	if ( (h = hite(x + PWID, z - PWID)) < max_h ) max_h = h;
	if ( (h = hite(x + PWID, z + PWID)) < max_h ) max_h = h;
	return ( max_h );
	}

static
void	move_player()
	{
	int	type, limit;
	int	old_py;
	int	x, z, nu, nq, h;
	int	lm;

	type = sqrs [qx][qz].sqr_type;

	h = height(qx * SIDE + pux, qz * SIDE + puz);

	if ( sqrs [qx][qz].sqr_y == py && (type & T_VISIBLE) == 0 )
		/* dead */
		{
		dead = TRUE;
		return;
		}

	old_py = py;

	if ( joystick & RK_SPACE )
		/* rise if possible */
		{
		limit = ( (type & TD_BLOWER) == TD_BLOWER ) ?
			10 : h - 12;
		if ( py > limit && (type & T_VISIBLE) )
			py -= 2;
		else if ( py < limit || (type & T_VISIBLE) == 0 )
			py += 2;
		lm = ASCEND;
		}
	else if ( py < h )
		/* fall */
		{
		py += 2;
		lm = DESCEND;
		}
	else
		lm = 0;

	if ( (joystick & (RK_UP|RK_DOWN)) == RK_UP )
/*...sup:16:*/
	{
lm |= RK_UP;

x = (sx + pqx) * SIDE + pux;

if ( pqz == SQR_PER_SCN - 1 && puz == SIDE - CLOSE )
	/* attempt to leave via back face of screen */
	{
	if ( (sqrs [qx][qz].sqr_type & T_ARROW_UP) &&
	     sqrs [qx][qz].sqr_y == py &&
	     fit_through_door(pux) )
		{
		if ( ++psz >= SCN_ARRAY_Z_SZ )
			psz = 0;
		pqz = 0;
		puz = HALF;
		}
	}
else
	{
	if ( (nu = puz + STEP) == SIDE )
		/* to new square */
		{
		nu = 0; nq = pqz + 1;
		}
	else
		/* in same square */
		nq = pqz;

	if ( height(x, (sz + nq) * SIDE + nu) >= py )
		/* can move to lower or same height place */
		{
		pqz = nq; puz = nu;
		}
	}
	}
/*...e*/
	else if ( (joystick & (RK_UP|RK_DOWN)) == RK_DOWN )
/*...sdown:16:*/
	{
lm |= RK_DOWN;

x = (sx + pqx) * SIDE + pux;

if ( pqz == 0 && puz == CLOSE )
	/* attempt to leave via back face of screen */
	{
	if ( (sqrs [qx][qz].sqr_type & T_ARROW_DOWN) &&
	     sqrs [qx][qz].sqr_y == py &&
	     fit_through_door(pux) )
		{
		if ( --psz < 0 )
			psz = SCN_ARRAY_Z_SZ - 1;
		pqz = SQR_PER_SCN - 1;
		puz = HALF;
		}
	}
else
	{
	if ( (nu = puz - STEP) == -STEP )
		/* to new square */
		{
		nu = SIDE - STEP; nq = pqz - 1;
		}
	else
		/* in same square */
		nq = pqz;

	if ( height(x, (sz + nq) * SIDE + nu) >= py )
		/* can move to lower or same height place */
		{
		pqz = nq; puz = nu;
		}
	}
	}
/*...e*/

	calc_p_q_z();

	if ( (joystick & (RK_RIGHT|RK_LEFT)) == RK_RIGHT )
/*...sright:16:*/
	{
lm |= RK_RIGHT;

z = (sz + pqz) * SIDE + puz;

if ( pqx == SQR_PER_SCN - 1 && pux == SIDE - CLOSE )
	/* attempt to leave via back face of screen */
	{
	if ( (sqrs [qx][qz].sqr_type & T_ARROW_RIGHT) &&
	     sqrs [qx][qz].sqr_y == py &&
	     fit_through_door(puz) )
		{
		if ( ++psx >= SCN_ARRAY_X_SZ )
			psx = 0;
		pqx = 0;
		pux = HALF;
		}
	}
else
	{
	if ( (nu = pux + STEP) == SIDE )
		/* to new square */
		{
		nu = 0; nq = pqx + 1;
		}
	else
		/* in same square */
		nq = pqx;

	if ( height((sx + nq) * SIDE + nu, z) >= py )
		/* can move to lower or same height place */
		{
		pqx = nq; pux = nu;
		}
	}
	}
/*...e*/
	else if ( (joystick & (RK_RIGHT|RK_LEFT)) == RK_LEFT )
/*...sleft:16:*/
	{
lm |= RK_LEFT;

z = (sz + pqz) * SIDE + puz;

if ( pqx == 0 && pux == CLOSE )
	/* attempt to leave via back face of screen */
	{
	if ( (sqrs [qx][qz].sqr_type & T_ARROW_LEFT) &&
	     sqrs [qx][qz].sqr_y == py &&
	     fit_through_door(puz) )
		{
		if ( --psx < 0 )
			psx = SCN_ARRAY_X_SZ - 1;
		pqx = SQR_PER_SCN - 1;
		pux = HALF;
		}
	}
else
	{
	if ( (nu = pux - STEP) == -STEP )
		/* to new square */
		{
		nu = SIDE - STEP; nq = pqx - 1;
		}
	else
		/* in same square */
		nq = pqx;

	if ( height((sx + nq) * SIDE + nu, z) >= py )
		/* can move to lower or same height place */
		{
		pqx = nq; pux = nu;
		}
	}
	}
/*...e*/

	calc_p_q_x();

	if ( lm )
		last_move = lm;
	}
/*...e*/
/*...splayer_firing:0:*/
static
void	move_posn(p_x, p_y, p_z, dir, h, v)
int	*p_x, *p_y, *p_z, dir, h, v;
	{
	if ( (dir & (RK_UP|RK_DOWN)) == RK_UP )
		*p_z += h;
	else if ( (dir & (RK_UP|RK_DOWN)) == RK_DOWN )
		*p_z -= h;

	if ( (dir & (RK_LEFT|RK_RIGHT)) == RK_RIGHT )
		*p_x += h;
	else if ( (dir & (RK_LEFT|RK_RIGHT)) == RK_LEFT )
		*p_x -= h;

	if ( (dir & (ASCEND|DESCEND)) == DESCEND )
		*p_y += v;
	else if ( (dir & (ASCEND|DESCEND)) == ASCEND )
		*p_y -= v;
	}

#define	BOLT_STEP_H	4
#define	BOLT_STEP_V	4
#define	BOLT_LENGTH_H	5
#define	BOLT_LENGTH_V	5
#define	BOLT_EXP_H	2
#define	BOLT_EXP_V	2

static
BOOLEAN	absorbed(x, y, z)		/* returns TRUE if off screen or     */
					/* inside a solid block              */
int	x, y, z;
	{
	if ( x < BOLT_EXP_H || x > SQR_PER_SCN * SIDE - BOLT_EXP_H ||
	     z < BOLT_EXP_H || z > SQR_PER_SCN * SIDE - BOLT_EXP_H ||
	     y < BOLT_EXP_V || y > 80 - BOLT_EXP_V )
		return ( TRUE );

	/* do the more complicated test */

	return ( (sqrs [sx + x / SIDE][sz + z / SIDE].sqr_type & (T_SOLID|T_POLE)) &&
		  sqrs [sx + x / SIDE][sz + z / SIDE].sqr_y < y );
	}

static
BOOLEAN	hit_bolt(b)
BASTARD	*b;
	{
	int	x, y, z;

	return ( (x = b -> bastard_x) <= lx2 + QTR && x >= lx2 - QTR &&
		 (z = b -> bastard_z) <= lz2 + QTR && z >= lz2 - QTR &&
		 (y = b -> bastard_y) <= ly2 + 10  && y >= ly2 - 10 );
	}

static
BOOLEAN	hit_a_bastard()
	{
	BOOLEAN	got_one = FALSE;
	BASTARD	*b;
	int	i, state;

	b = bastards;
	for ( i = 0; i < n_bastards; i++ )
		{
		if ( ((state = b -> bastard_state) & B_ALIVE) && hit_bolt(b) )
			/* collision occured */
			{
			got_one = TRUE;
			if ( state & B_KILLABLE )
				{
				state &= ~B_ALIVE;
				state |= B_DYING;
				b -> bastard_state = state;
				b -> bastard_cnt = 10;
				add_score(SCORE_KILL);
				}
			}
		b++;
		}
	return ( got_one );
	}

static
void	player_firing()
	{
	if ( firing )
		/* deal with existing lazer bolt */
		{
		move_posn(&lx2, &ly2, &lz2, l_direction,
			  BOLT_STEP_H, BOLT_STEP_V);
		if ( hit_a_bastard() || absorbed(lx2, ly2, lz2) )
			{
			int	x1, y1, z1, x2, y2, z2;

			firing = FALSE;

			x1 = lx2 - BOLT_EXP_H; x2 = lx2 + BOLT_EXP_H;
			y1 = ly2 - BOLT_EXP_V; y2 = ly2 + BOLT_EXP_V;
			z1 = lz2 - BOLT_EXP_H; z2 = lz2 + BOLT_EXP_H;

			/* display an "explosion" */

			zap(x1, y1, z1, x2, y2, z2);
			zap(x1, y2, z1, x2, y1, z2);
			zap(x2, y1, z1, x1, y2, z2);
			zap(x2, y2, z1, x1, y1, z2);

			zap(lx2, ly2, z1, lx2, ly2, z2);
			zap(lx2, y1, lz2, lx2, y2, lz2);
			zap(x1, ly2, lz2, x2, ly2, lz2);

			se_foreground(EFFECT_FIRE_2, 7);
			}
		else
			{
			move_posn(&lx1, &ly1, &lz1, l_direction,
				  BOLT_STEP_H, BOLT_STEP_V);
			zap(lx1, ly1 + 1, lz1, lx2, ly2 + 1, lz2);
			zap(lx1, ly1, lz1, lx2, ly2, lz2);
			zap(lx1, ly1 - 1, lz1, lx2, ly2 - 1, lz2);
			}
		}
	else if ( (joystick & RK_INS) != 0 && last_move != 0 )
		/* starting to fire */
		{
		lx1 = lx2 = px;
		ly1 = ly2 = py - 5;
		lz1 = lz2 = pz;
		move_posn(&lx2, &ly2, &lz2, l_direction = last_move,
			  BOLT_LENGTH_H, BOLT_LENGTH_V);
		if ( !absorbed(lx2, ly2, lz2) )
			{
			firing = TRUE;
			se_foreground(EFFECT_FIRE_1, 7);
			}
		}
	}
/*...e*/
/*...splayer_set_sqr:0:*/
static BOOLEAN player_set_sqr(void)
	{
	if ( (sqrs [qx][qz].sqr_type & (T_SETABLE|T_SET)) == T_SETABLE &&
	     py == sqrs [qx][qz].sqr_y )
		{
		sqrs [qx][qz].sqr_type |= T_SET;
		n_set++;
		add_score(SCORE_SET);
		return ( TRUE );
		}
	return ( FALSE );
	}
/*...e*/
/*...splayer_hit_x:0:*/
static BOOLEAN player_hit_x(void)
	{
	if ( (sqrs [qx][qz].sqr_type & (T_X|T_USED)) == T_X &&
	     py == sqrs [qx][qz].sqr_y &&
	     x_factor + 1000 < 10000 )
		{
		sqrs [qx][qz].sqr_type |= T_USED;
		x_factor += 1000;
		display_stats();
		se_foreground(EFFECT_X, 2);
		return TRUE;
		}
	return FALSE;
	}
/*...e*/
/*...splayer_hit_teleport:0:*/
static BOOLEAN player_hit_teleport(void)
	{
	int	x, z;
	char	teleport_num;

	/* test for source teleport */
	/* must not have a minus on it */
	if ( joystick & RK_SPACE &&
	     (sqrs [qx][qz].sqr_type & (T_DIAMOND|T_MINUS)) == T_DIAMOND && py == sqrs [qx][qz].sqr_y - 2 )
		{
		teleport_num = sqrs [qx][qz].sqr_num;

/* $$$ HACK */

	if ( joystick & RK_INS )
		{
		char	s [5];

		rectangle_around(1, Y_CHAR - 3, 6, Y_CHAR - 1, G_3);
		g_s_print("GO TO", 1, Y_CHAR - 3, G_5);
		get_string(s, 5, 1, Y_CHAR - 2, G_15, G_3);
		teleport_num = atoi(s);
		}

/* $$$ END HACK */

		for ( x = 0; x < SQR_ARRAY_X_SZ; x++ )
			for ( z = 0; z < SQR_ARRAY_Z_SZ; z++ )
				/* now test to find destination teleport */
				/* must not have a bar on it */
				if ( (sqrs [x][z].sqr_type & (T_DIAMOND|T_BAR)) == T_DIAMOND &&
				     sqrs [x][z].sqr_num == teleport_num &&
				     (x != qx || z != qz) )
					/* found destination */
					{
					py  = sqrs [x][z].sqr_y;
					pux = HALF;
					puz = HALF;
					pqx = x % SQR_PER_SCN;
					pqz = z % SQR_PER_SCN;
					psx = x / SQR_PER_SCN;
					psz = z / SQR_PER_SCN;
					calc_p_q_x();
					calc_p_q_z();
					se_foreground(EFFECT_TELE, 5);
					return ( TRUE );
					}
		}
	return ( FALSE );
	}
/*...e*/
/*...sbastards:0:*/
#define	SLOW		1
#define	FAST		2

#define	DIR_UP		0x0001
#define	DIR_DOWN	0x0002
#define	DIR_LEFT	0x0004
#define	DIR_RIGHT	0x0008

/*...sset_dir\44\ set_ud_dir\44\ set_lr_dir \45\ dir guaranteed not to point offscreen:0:*/
static char all_4_dirs [] = { DIR_UP, DIR_DOWN, DIR_LEFT, DIR_RIGHT };
static char ud_dirs    [] = { DIR_UP, DIR_DOWN };
static char lr_dirs    [] = { DIR_LEFT, DIR_RIGHT };

static void set_dir_from(BASTARD *b, char dir)
	{
	int	x, z;

	if ( (x = b -> bastard_x) < SIDE && dir == DIR_LEFT )
		dir ^= (DIR_LEFT|DIR_RIGHT);
	else if ( x >= SQR_PER_SCN * SIDE - SIDE && dir == DIR_RIGHT )
		dir ^= (DIR_LEFT|DIR_RIGHT);
	if ( (z = b -> bastard_z) < SIDE && dir == DIR_DOWN )
		dir ^= (DIR_DOWN|DIR_UP);
	else if ( z >= SQR_PER_SCN * SIDE - SIDE && dir == DIR_UP )
		dir ^= (DIR_DOWN|DIR_UP);
	b -> bastard_dir = dir;
	}

#define	set_dir(b)	set_dir_from(b, all_4_dirs [random()&3]);
#define	set_ud_dir(b)	set_dir_from(b, ud_dirs [random()&1]);
#define	set_lr_dir(b)	set_dir_from(b, lr_dirs [random()&1]);
/*...e*/
/*...sbastard_blocked \45\ use this after choosing dir:0:*/
static BOOLEAN bastard_blocked(BASTARD *b)
					/* can it eventually get to square   */
					/* to its left or side etc.          */
	{
	int	x, z;
	char	dir;

	x = b -> bastard_x / SIDE;
	z = b -> bastard_z / SIDE;

	if ( (dir = b -> bastard_dir) & DIR_LEFT )
		x--;
	else if ( dir & DIR_RIGHT )
		x++;
	if ( dir & DIR_DOWN )
		z--;
	else if ( dir & DIR_UP )
		z++;
	return ( b -> bastard_y > sqrs [sx + x][sz + z].sqr_y );
	}
/*...e*/
/*...smove_bastard \45\ uses bastard_dir field:0:*/
static void move_bastard(BASTARD *b, int step)
					/* calc new positions using dir */
	{
	char	dir;

	if ( (dir = b -> bastard_dir) & DIR_LEFT )
		b -> bastard_x -= step;
	else if ( dir & DIR_RIGHT )
		b -> bastard_x += step;
	if ( dir & DIR_DOWN )
		b -> bastard_z -= step;
	else if ( dir & DIR_UP )
		b -> bastard_z += step;
	}
/*...e*/
/*...sbastard_suicide \45\ what to call to kill bastard:0:*/
static void bastard_suicide(BASTARD *b)		/* set to dying */
	{
	b -> bastard_state &= ~B_ALIVE;
	b -> bastard_state |= B_DYING;
	b -> bastard_cnt    = 10;
	}
/*...e*/
/*...stouch_player \45\ test point with player:0:*/
static BOOLEAN touch_player(int x, int y, int z, int range)
	{
	return ( x_in_range_of_player(x, range) &&
		 y_in_range_of_player(y, 10)  &&
		 z_in_range_of_player(z, range) );
	}
/*...e*/
/*...shit_player \45\ test bastard centre with player:0:*/
static BOOLEAN hit_player(BASTARD *b)
	{
	return ( x_in_range_of_player(b -> bastard_x, QTR) &&
		 y_in_range_of_player(b -> bastard_y, 10)  &&
		 z_in_range_of_player(b -> bastard_z, QTR) );
	}
/*...e*/
/*...son_edge_of_screen \45\ can bastard go no further:0:*/
static BOOLEAN on_edge_of_screen(BASTARD *b)
	{
	int	x, z;

	x = b -> bastard_x;
	z = b -> bastard_z;
	return ( x == HALF || x == SQR_PER_SCN * SIDE - HALF ||
		 z == HALF || z == SQR_PER_SCN * SIDE - HALF );
	}
/*...e*/

/*...smove_ball:0:*/
static void move_ball(BASTARD *b)
	{
	int	x, z, cnt, c, s;

	switch ( b -> bastard_mode )
		{
		case 0:
			/* newly initialised and choose new dir */
			set_dir(b);
			if ( bastard_blocked(b) )
				b -> bastard_mode = 2 + (random() & 1);		
							/* going up */
			else
				{
				b -> bastard_mode = 1;	/* start moving */
				b -> bastard_cnt = SIDE;
				}
			break;
		case 1:
			/* move in direction for cnt times */
			if ( modulo_8 & 1 )
				{
				move_bastard(b, SLOW);
				if ( !(b -> bastard_cnt -= SLOW) )
					b -> bastard_mode = ( random() & 1 ) ?
						4 :		/* down */
						0;		/* choose direction */
				}
			break;
		case 2:
			/* going up until not blocked */
			--(b -> bastard_y);
			if ( (rand() & 3) == 0 )
				{
				b -> bastard_mode = 5;	/* firing */
				b -> bastard_cnt = b -> bastard_y;
				}

			if ( !bastard_blocked(b) )
				{
				b -> bastard_mode = 1;	/* start moving */
				b -> bastard_cnt = SIDE;
				}
			break;
		case 3:
			/* keep going up until hit ceiling */
			if ( (rand() & 3) == 0 )
				{
				b -> bastard_mode = 5;	/* firing */
				b -> bastard_cnt = b -> bastard_y;
				}

			if ( --(b -> bastard_y) == 5 )
				{
				b -> bastard_mode = 1;	/* start moving */
				b -> bastard_cnt = SIDE;
				se_foreground(EFFECT_HIT, 0);
				}
			break;
		case 4:
			/* keep going down until hit floor */
			if ( b -> bastard_y < sqrs [sx + b -> bastard_x/SIDE]
			     [sz + b -> bastard_z/SIDE].sqr_y )
				++(b -> bastard_y);
			else
				{
				b -> bastard_mode = 0;	/* choose direction */
				se_foreground(EFFECT_HIT, 0);
				}
			break;
		case 5:
			/* shooting mode */
			x   = b -> bastard_x;
			z   = b -> bastard_z;
			cnt = b -> bastard_cnt;
			c   = rough_cos(cnt)/2;
			s   = rough_sin(cnt)/2;
			if ( cnt == sqrs [sx + x/SIDE][sz + z/SIDE].sqr_y )
				{
				se_foreground(EFFECT_ZAP, 3);
				zap(x + c, cnt, z + s, x + 2 * c, cnt, z + 2 * s);
				zap(x - s, cnt, z + c, x - 2 * s, cnt, z + 2 * c);
				zap(x - c, cnt, z - s, x - 2 * c, cnt, z - 2 * s);
				zap(x + s, cnt, z - c, x + 2 * s, cnt, z - 2 * c);
				b -> bastard_mode = 0;	/* choose dir */
				}
			else
				{
				b -> bastard_cnt++;
				zap(x, b -> bastard_y, z, x + c, cnt, z + s);
				zap(x, b -> bastard_y, z, x - s, cnt, z + c);
				zap(x, b -> bastard_y, z, x - c, cnt, z - s);
				zap(x, b -> bastard_y, z, x + s, cnt, z - c);
				if ( touch_player(x, cnt, z, QTR) )
					dead = TRUE;
				}
		}
	if ( hit_player(b) )
		dead = TRUE;
	}
/*...e*/
/*...smove_star:0:*/
static void move_star(BASTARD *b)
	{
	int	x, z, cnt;

	switch ( b -> bastard_mode )
		{
		case 0:
			/* newly initialised and choose new dir */
			set_dir(b);
			if ( bastard_blocked(b) )
				{
				if ( rand() & 1 )
					b -> bastard_mode = 2;	/* going up */
				}
			else
				{
				b -> bastard_mode = 1;	/* start moving */
				b -> bastard_cnt = SIDE;
				}
			break;
		case 1:
			/* move in direction for cnt times */
			move_bastard(b, SLOW);
			if ( !(b -> bastard_cnt -= SLOW) )
				b -> bastard_mode = 3;	/* down */
			break;
		case 2:
			/* going up until not blocked */
			--(b -> bastard_y);
			if ( !bastard_blocked(b) )
				{
				b -> bastard_mode = 1;	/* start moving */
				b -> bastard_cnt = SIDE;
				}
			break;
		case 3:
			/* keep going down until hit floor */
			if ( (rand() & 3) == 0 )
				{
				b -> bastard_mode = 4;	/* shooting */
				b -> bastard_cnt = b -> bastard_y;
				}
			else if ( b -> bastard_y < sqrs [sx + b -> bastard_x/SIDE]
			     [sz + b -> bastard_z/SIDE].sqr_y )
				++(b -> bastard_y);
			else
				{
				b -> bastard_mode = 0;	/* choose direction */
				se_foreground(EFFECT_HIT, 0);
				}
			break;
		case 4:
			/* shooting */
			x   = b -> bastard_x;
			z   = b -> bastard_z;
			cnt = b -> bastard_cnt;
			if ( cnt >= sqrs [sx + x/SIDE][sz + z/SIDE].sqr_y )
				{
				zap(x + QTR, cnt, z + QTR, x + HALF, cnt, z + HALF);
				zap(x - QTR, cnt, z + QTR, x - HALF, cnt, z + HALF);
				zap(x - QTR, cnt, z - QTR, x - HALF, cnt, z - HALF);
				zap(x + QTR, cnt, z - QTR, x + HALF, cnt, z - HALF);
				se_foreground(EFFECT_ZAP, 3);
				b -> bastard_mode = 0;	/* choose dir */
				}
			else
				{
				b -> bastard_cnt += 3;
				zap(x + QTR, cnt - 10, z + QTR, x + QTR, cnt, z + QTR);
				zap(x - QTR, cnt - 10, z + QTR, x - QTR, cnt, z + QTR);
				zap(x - QTR, cnt - 10, z - QTR, x - QTR, cnt, z - QTR);
				zap(x + QTR, cnt - 10, z - QTR, x + QTR, cnt, z - QTR);
				if ( touch_player(x, cnt, z, QTR) )
					dead = TRUE;
				}
			break;
		}
	if ( hit_player(b) )
		dead = TRUE;
	}
/*...e*/
/*...smove_tri:0:*/
static void move_tri(BASTARD *b)
	{
	int	x, z, cnt;

	switch ( b -> bastard_mode )
		{
		case 0:
			/* newly initialised and choose new dir */
			set_dir(b);
			if ( bastard_blocked(b) )
				{
				if ( rand() & 1 )
					b -> bastard_mode = 2;	/* going up */
				}
			else
				{
				b -> bastard_mode = 1;	/* start moving */
				b -> bastard_cnt = SIDE;
				}
			break;
		case 1:
			/* move in direction for cnt times */
			move_bastard(b, SLOW);
			if ( !(b -> bastard_cnt -= SLOW) )
				b -> bastard_mode = 3;	/* down */
			break;
		case 2:
			/* going up until not blocked */
			--(b -> bastard_y);
			if ( !bastard_blocked(b) )
				{
				b -> bastard_mode = 1;	/* start moving */
				b -> bastard_cnt = SIDE;
				}
			break;
		case 3:
			/* keep going down until hit floor */
			if ( (rand() & 3) == 0 )
				{
				b -> bastard_mode = 4;	/* shooting */
				b -> bastard_cnt = b -> bastard_y;
				}
			else if ( b -> bastard_y < sqrs [sx + b -> bastard_x/SIDE]
			     [sz + b -> bastard_z/SIDE].sqr_y )
				++(b -> bastard_y);
			else
				{
				se_foreground(EFFECT_HIT, 0);
				b -> bastard_mode = 0;	/* choose direction */
				}
			break;
		case 4:
			/* shooting */
			x   = b -> bastard_x;
			z   = b -> bastard_z;
			cnt = b -> bastard_cnt;
			if ( cnt >= sqrs [sx + x/SIDE][sz + z/SIDE].sqr_y )
				{
				zap(x + QTR, cnt, z + QTR, x + HALF, cnt - 5, z + HALF);
				zap(x - QTR, cnt, z + QTR, x - HALF, cnt - 5, z + HALF);
				zap(x - QTR, cnt, z - QTR, x - HALF, cnt - 5, z - HALF);
				zap(x + QTR, cnt, z - QTR, x + HALF, cnt - 5, z - HALF);
				se_foreground(EFFECT_ZAP, 3);
				b -> bastard_mode = 0;	/* choose dir */
				}
			else
				{
				b -> bastard_cnt += 2;
				zap(x, b -> bastard_y, z, x + QTR, cnt, z + QTR);
				zap(x, b -> bastard_y, z, x - QTR, cnt, z + QTR);
				zap(x, b -> bastard_y, z, x - QTR, cnt, z - QTR);
				zap(x, b -> bastard_y, z, x + QTR, cnt, z - QTR);
				if ( touch_player(x, cnt, z, QTR) )
					dead = TRUE;
				}
			break;
		}
	if ( hit_player(b) )
		dead = TRUE;
	}
/*...e*/
/*...smove_ud:0:*/
static void move_ud(BASTARD *b)
	{
	int	x, y, z, gnd;

	switch ( b -> bastard_mode )
		{
		case 0:
			/* while above floor go down */
			if ( b -> bastard_y < sqrs [sx + b -> bastard_x/SIDE]
			     [sz + b -> bastard_z/SIDE].sqr_y )
				++(b -> bastard_y);
			else
				b -> bastard_mode = 1;	/* choose dir */
			break;
		case 1:
			/* choose direction */
			set_ud_dir(b);
			if ( bastard_blocked(b) )
				{
				bastard_suicide(b);
				se_foreground(EFFECT_FIRE_2, 5);
				}
			else
				b -> bastard_mode = 2;	/* move */
			break;
		case 2:
			/* move in direction until blocked */
			move_bastard(b, SLOW);
			if ( on_edge_of_screen(b) )
				b -> bastard_mode = 4;	/* up to top */
			else if ( bastard_blocked(b) )
				b -> bastard_mode = 3;	/* up */
			break;
		case 3:
			/* going up until not blocked */
			--(b -> bastard_y);
			if ( !bastard_blocked(b) )
				b -> bastard_mode = 2;	/* start moving */
			break;
		case 4:
			/* keep going up until hit roof */
			if ( b -> bastard_y > 5 )
				(b -> bastard_y)--;
			else
				{
				b -> bastard_dir ^= (DIR_UP|DIR_DOWN);
				move_bastard(b, SLOW);
				b -> bastard_mode = 5;	/* fly across top */
				se_foreground(EFFECT_HIT, 3);
				}
			break;
		case 5:
			/* flying across the top */
			if ( (random() & 0x1f) == 0 || on_edge_of_screen(b) )
				{
				b -> bastard_mode = 0;	/* down */
				se_foreground(EFFECT_HIT, 3);
				}
			else
				move_bastard(b, SLOW);

			if ( modulo_8 & 4 )
				{
				/* shooting */
				x   = b -> bastard_x;
				y   = b -> bastard_y;
				z   = b -> bastard_z;
				gnd = sqrs [sx + x/SIDE][sz + z/SIDE].sqr_y;
				zap(x, y, z, x, gnd, z);
				zap(x - EIGHTH, y, z, x - QTR, gnd, z);
				zap(x + EIGHTH, y, z, x + QTR, gnd, z);
				se_foreground(EFFECT_ZAP, 3);
				if ( touch_player(x, gnd, z, QTR) )
					dead = TRUE;
				}
			break;
		}
	if ( hit_player(b) )
		dead = TRUE;
	}
/*...e*/
/*...smove_lr:0:*/
static void move_lr(BASTARD *b)
	{
	int	x, y, z, gnd;

	switch ( b -> bastard_mode )
		{
		case 0:
			/* while above floor go down */
			if ( b -> bastard_y < sqrs [sx + b -> bastard_x/SIDE]
			     [sz + b -> bastard_z/SIDE].sqr_y )
				++(b -> bastard_y);
			else
				b -> bastard_mode = 1;	/* choose dir */
			break;
		case 1:
			/* choose direction */
			set_lr_dir(b);
			if ( bastard_blocked(b) )
				{
				bastard_suicide(b);
				se_foreground(EFFECT_FIRE_2, 5);
				}
			else
				b -> bastard_mode = 2;	/* move */
			break;
		case 2:
			/* move in direction until blocked */
			move_bastard(b, SLOW);
			if ( on_edge_of_screen(b) )
				b -> bastard_mode = 4;	/* up to top */
			else if ( bastard_blocked(b) )
				b -> bastard_mode = 3;	/* up */
			break;
		case 3:
			/* going up until not blocked */
			--(b -> bastard_y);
			if ( !bastard_blocked(b) )
				b -> bastard_mode = 2;	/* start moving */
			break;
		case 4:
			/* keep going up until hit roof */
			if ( b -> bastard_y > 5 )
				(b -> bastard_y)--;
			else
				{
				b -> bastard_dir ^= (DIR_LEFT|DIR_RIGHT);
				move_bastard(b, SLOW);
				b -> bastard_mode = 5;	/* fly across top */
				se_foreground(EFFECT_HIT, 3);
				}
			break;
		case 5:
			/* flying across the top */
			if ( (random() & 0x1f) == 0 || on_edge_of_screen(b) )
				{
				b -> bastard_mode = 0;	/* down */
				se_foreground(EFFECT_HIT, 3);
				}
			else
				move_bastard(b, SLOW);

			if ( modulo_8 & 4 )
				{
				/* shooting */
				x   = b -> bastard_x;
				y   = b -> bastard_y;
				z   = b -> bastard_z;
				gnd = sqrs [sx + x/SIDE][sz + z/SIDE].sqr_y;
				zap(x, y, z, x, gnd, z);
				zap(x, y, z - EIGHTH, x, gnd, z - QTR);
				zap(x, y, z + EIGHTH, x, gnd, z - QTR);
				se_foreground(EFFECT_ZAP, 3);
				if ( touch_player(x, gnd, z, QTR) )
					dead = TRUE;
				}
			break;
		}
	if ( hit_player(b) )
		dead = TRUE;
	}
/*...e*/

/*...smove_bastards:0:*/
static void move_bastards(void)
	{
	int	i, state, x, z;
	BASTARD	*b;

	b = bastards;
	for ( i = 0; i < n_bastards; i++ )
		{
		if ( (state = b -> bastard_state) & B_DYING )
			{
			if ( --(b -> bastard_cnt) == 0 )
				/* deader than a dodo */
				{
				b -> bastard_state &= ~B_DYING;
				b -> bastard_cnt = (rand() & 0x3f) + 10;
				}
			}
		else if ( state & B_ALIVE )
			switch ( b -> bastard_type )
				{
				case BT_BALL:	move_ball(b);	break;
				case BT_STAR:	move_star(b);	break;
				case BT_TRI:	move_tri(b);	break;
				case BT_UD:	move_ud(b);	break;
				case BT_LR:	move_lr(b);	break;
				}
		else
			/* bastard is dead */
			{
			if ( --(b -> bastard_cnt) == 0 )
				/* reincarnate bastard */
				{
				b -> bastard_state |= B_ALIVE;
				b -> bastard_mode   =
				b -> bastard_cnt    = 0;

				/* do not want bastard to reappear over you */

				do
					{
					x = (random() % (SQR_PER_SCN - 2) + 1) * SIDE + HALF;
					z = (random() % (SQR_PER_SCN - 2) + 1) * SIDE + HALF;
					}
				while ( x_in_range_of_player(x, HALF) &&
					z_in_range_of_player(z, HALF) );

				b -> bastard_x      = x;
				b -> bastard_z      = z;
				b -> bastard_y      = 5;
				}
			}
		b++;
		}
	}
/*...e*/
/*...e*/
/*...seach_cycle:0:*/
static void each_cycle(void)
	{
	int	old_x_factor;

	modulo_8 = ((modulo_8 + 1) & 0x0007);
	old_x_factor = x_factor;
	if ( !(--x_factor) )
		{
		dead = TRUE;
		return;
		}
	if ( x_factor >= 100 )
		se_background(EFFECT_OFF, 0);
	else
		se_background(EFFECT_LOW_X, 0);
	if ( (x_factor/100) != (old_x_factor/100) )
		display_stats();
	}
/*...e*/
/*...sdo_a_map:0:*/
static BOOLEAN is_teleport(int type)
	{
	return ( (type & TD_BLOWER) != TD_BLOWER && (type & T_DIAMOND) != 0 );
	}

static BOOLEAN is_x(int type)
	{
	return ( (type & T_X) != 0 && (type & T_USED) == 0 );
	}

static BOOLEAN is_minus(int type)
	{
	return ( (type & T_MINUS) != 0 && (type & (T_BAR|T_DIAMOND)) == 0 );
	}

static BOOLEAN is_set(int type)
	{
	return ( (type & T_SET) != 0 );
	}

static BOOLEAN is_setable(int type)
	{
	return ( (type & T_SETABLE) != 0 && (type & T_SET) == 0 );
	}

static BOOLEAN is_bar(int type)
	{
	return ( (type & T_BAR) != 0 && (type & (T_MINUS|T_DIAMOND)) == 0 );
	}

static BOOLEAN is_used(int type)
	{
	return ( (type & T_USED) != 0 );
	}

static BOOLEAN in_screen(int sx, int sz, BOOLEAN (*test)(int type))
	{
	int	x, z;

	for ( z = 0; z < SQR_PER_SCN; z++ )
		for ( x = 0; x < SQR_PER_SCN; x++ )
			if ( (*test)(sqrs [sx * SQR_PER_SCN + x]
					  [sz * SQR_PER_SCN + z].sqr_type) )
				return ( TRUE );
	return ( FALSE );
	}

static void tag(char *s, char *t, int x, int y, int col)
	{
	g_s_print(s, x, y, col);
	g_s_print(t, x + 2, y, G_5);
	}

static void do_a_map(void)
	{
	int	x, z, xi, zi, sc_x, sc_y, n_visited = 0;
	char	s [20];

	for ( z = 0; z < SCN_ARRAY_Z_SZ; z++ )
		for ( x = 0; x < SCN_ARRAY_X_SZ; x++ )
			n_visited += scns [x][z].scn_visited;

	sprintf(s, "%d%% VISITED",
		(n_visited * 100) / (SCN_ARRAY_X_SZ * SCN_ARRAY_Z_SZ));

	rectangle_around(X_CHAR/2 - 12, Y_CHAR/2 - 6, X_CHAR/2 + 12, Y_CHAR/2 + 6, G_3);
	g_s_print("MAP OF LOCAL AREA", X_CHAR/2 - 12, Y_CHAR/2 - 6, G_15);

	for ( z = -2; z <= 2; z++ )
		{
		zi = (SCN_ARRAY_Z_SZ + z + psz) % SCN_ARRAY_Z_SZ;
		for ( x = -2; x <= 2; x++ )
			{
			xi = (SCN_ARRAY_X_SZ + x + psx) % SCN_ARRAY_X_SZ;
			sc_x = X_CHAR/2 + x * 2 - 8;
			sc_y = Y_CHAR/2 - z * 2;
			if ( scns [xi][zi].scn_visited )
				{
				g_s_print("no", sc_x, sc_y, G_0);
				g_s_print("pq", sc_x, sc_y + 1, G_0);
				if ( in_screen(xi, zi, is_teleport) )
					g_s_print("r", sc_x, sc_y, G_15);
				if ( in_screen(xi, zi, is_x) )
					g_s_print("s", sc_x + 1, sc_y, G_15);
				else if ( in_screen(xi, zi, is_used) )
					g_s_print("s", sc_x + 1, sc_y, G_3);
				if ( in_screen(xi, zi, is_setable) )
					g_s_print("t", sc_x, sc_y + 1, G_15);
				else if ( in_screen(xi, zi, is_set) )
					g_s_print("t", sc_x, sc_y + 1, G_3);
				if ( in_screen(xi, zi, is_minus) )
					{
					g_s_print("xx", sc_x, sc_y, G_5);
					g_s_print("yy", sc_x, sc_y + 1, G_5);
					}
				if ( in_screen(xi, zi, is_bar) )
					{
					g_s_print("wv", sc_x, sc_y, G_5);
					g_s_print("wv", sc_x, sc_y + 1, G_5);
					}
				}
			}
		}

	rectangle_around(X_CHAR/2, Y_CHAR/2 - 2, X_CHAR/2 + 1, Y_CHAR/2 + 4, G_0);

	g_s_print(s, X_CHAR/2, Y_CHAR/2 - 4, G_5);

	tag("r",  "TELEPORT",   X_CHAR/2, Y_CHAR/2 - 2, G_15);
	tag("s",  "USED X",     X_CHAR/2, Y_CHAR/2 - 1, G_3);
	tag("s",  "UNUSED X",   X_CHAR/2, Y_CHAR/2,     G_15);
	tag("t",  "SET NODE",   X_CHAR/2, Y_CHAR/2 + 1, G_3);
	tag("t",  "UNSET NODE", X_CHAR/2, Y_CHAR/2 + 2, G_15);
	tag("wv", "MERIDIAN",   X_CHAR/2, Y_CHAR/2 + 3, G_5);

	g_s_print("PRESS A KEY", X_CHAR/2 + 1, Y_CHAR/2 + 5, G_15);

	rk_wait_release();
	while ( !rk_get_joystick() )
		;
	rk_wait_release();
	g_s_copy(SPR_PAGE, VIS_PAGE);
	}
/*...e*/
/*...smain_loop:0:*/
static int dissolve [] = { SPR_P1_0, SPR_P2_0, SPR_P3_0, SPR_P4_0 };

#define	N_DISSOLVE	(sizeof(dissolve)/sizeof(int))

static void dissolve_player(int px, int py, int pz)
	{
	int	i;

	g_s_update(SPR_PAGE);
	for ( i = 0; i < N_DISSOLVE; i++ )
		{
		g_clear_spr();
		undisplay_bastards();
		undisplay_player(px, py, pz);
		precalc_bastards();
		precalc_player(px, py, pz, dissolve [i]);
		g_sprites();
		frame_delay(4);
		g_sync();
		g_refresh(SPR_PAGE);
		}
	}

static void undissolve_player(int px, int py, int pz)
	{
	int	i;

	g_s_update(SPR_PAGE);
	for ( i = N_DISSOLVE - 1; i >= 0; i-- )
		{
		g_clear_spr();
		undisplay_bastards();
		undisplay_player(px, py, pz);
		precalc_bastards();
		precalc_player(px, py, pz, dissolve [i]);
		g_sprites();
		frame_delay(4);
		g_sync();
		g_refresh(SPR_PAGE);
		}
	}

static BOOLEAN main_loop(void)			/* return FALSE if aborted */
	{
	int	counter, last_counter;
	BOOLEAN	diff_screen, set_sqr, set_x, hit_teleport;
	int	old_px, old_py, old_pz;
	int	i;

	old_psx = psx;
	old_psz = psz;
	initialise_screen();
	display_scn(psx, psz);
	g_s_copy(ALT_PAGE, SPR_PAGE);
	g_s_copy(ALT_PAGE, VIS_PAGE);
	se_foreground(EFFECT_NEW_SCN, 5);

	last_counter = tk_get_counter();

	while ( !dead &&
	        ((joystick = rk_get_joystick()) & RK_ESC) == 0 &&
	        n_set < n_setable )
		{
		if ( joystick & RK_RET )
			do_a_map();

		each_cycle();

		if ( dead )
			break;

		old_px = pqx * SIDE + pux;	/* old coords on screen */
		old_pz = pqz * SIDE + puz;
		old_py = py;

		move_player();
		player_firing();
		set_sqr = player_set_sqr();
		set_x   = player_hit_x();
		if ( hit_teleport = player_hit_teleport() )
			/* dissolve player */
			{
			remove_old_rays();
			display_new_rays();
			dissolve_player(old_px, old_py, old_pz);
			}

		if ( diff_screen = (psx != old_psx || psz != old_psz) )
			{
			old_psx = psx;
			old_psz = psz;
			initialise_screen();
			display_scn(psx, psz);
			g_s_copy(ALT_PAGE, SPR_PAGE);
			g_s_copy(ALT_PAGE, VIS_PAGE);
			se_foreground(EFFECT_NEW_SCN, 5);
			}
		else if ( set_sqr || set_x )
			{
			se_background(EFFECT_SETTING, 10);
			display_scn(psx, psz);
			g_s_copy(ALT_PAGE, SPR_PAGE);
			g_s_copy(ALT_PAGE, VIS_PAGE);
			se_background(EFFECT_OFF, 10);
			}

		move_bastards();

		g_s_update(SPR_PAGE);

		g_clear_spr();
		undisplay_bastards();
		undisplay_player(old_px, old_py, old_pz);
		precalc_bastards();		
		precalc_player(px, py, pz, SPR_PLAYER_0);
		g_sprites();

		g_s_update(VIS_PAGE);

		while ( (counter = tk_get_counter()) == last_counter )
			;
		g_sync();

		last_counter = counter;

		g_refresh(SPR_PAGE);

		remove_old_rays();
		display_new_rays();

		if ( hit_teleport )
			/* undissolve player */
			{
			undissolve_player(px, py, pz);
			g_clear_spr();
			undisplay_bastards();
			undisplay_player(px, py, pz);
			precalc_bastards();
			precalc_player(px, py, pz, SPR_PLAYER_0);
			g_sprites();
			g_sync();
			g_refresh(SPR_PAGE);
			}

		g_s_update(VIS_PAGE);
		}

	if ( dead )
		/* user died - arrrrgh ! */
		{
		se_foreground(EFFECT_DEAD, 10);
		remove_old_rays();
		display_new_rays();
		dissolve_player(px, py, pz);
		return ( TRUE );
		}
	else if ( n_set == n_setable )
		/* user set all squares - yahoo! */
		return ( TRUE );
	else
		/* user pressed escape */
		return ( FALSE );
	}
/*...e*/
/*...sgame:0:*/
static BOOLEAN game(void)
	{
	initialise_game();
	do
		{
		initialise_life();
		if ( !main_loop() )
			return ( FALSE );
		frame_delay(50);
		}
	while ( lives && n_set < n_setable );
	return ( TRUE );
	}
/*...e*/
/*...stop_level:0:*/
static void top_level(void)
	{
	BOOLEAN	ok;
	int	choice;

	hs_init(HS_FN);
	initialise_dist_table();
	while ( (choice = title_page()) != RK_ESC )
		switch ( choice )
			{
			case RK_SPACE:
				se_on();
				ok = game();
				se_foreground(EFFECT_OFF, 10);
				se_background(EFFECT_OFF, 10);
				se_off();
				if ( n_set == n_setable ||
				     joystick == (RK_LEFT|RK_RIGHT) )
					reward();
				if ( ok )
					update_hiscores();
				else
					aborted_message();
				break;
			case RK_RET:
				display_hiscores();
				break;
			}
	hs_deinit(HS_FN);
	}
/*...e*/
/*...smain:0:*/
int main(int argc, char *argv[])
	{
	FILE	*fp;

	if ( (fp = fopen("maze.cm", "rb")) == NULL )
		fatal("can't find maze.cm");
	fread(sqrs, sizeof(sqrs), 1, fp);
	fread(scns, sizeof(scns), 1, fp);
	fclose(fp);

	if ( !g_init() )
		fatal("cannot load sprites or character set");

	if ( !se_init() )
		{
		g_deinit();
		fatal("cannot load sound effects");
		}

	if ( !mus_init() )
		{
		se_deinit();
		g_deinit();
		fatal("cannot load music");
		}

	tk_init();
	rk_init();

	srandom((int) time(NULL));

	top_level();

	tk_deinit();
	rk_deinit();
	mus_deinit();
	se_deinit();
	g_deinit();

	return 0;
	}
/*...e*/
